package com.gmall.gateway.filters;

import com.gmall.common.utils.IpUtils;
import com.gmall.common.utils.JwtUtils;
import com.gmall.gateway.config.JwtProperties;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@EnableConfigurationProperties(JwtProperties.class)
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.PathConfig> {
    
    @Autowired
    private JwtProperties properties;

    public AuthGatewayFilterFactory() {
        super(PathConfig.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public GatewayFilter apply(PathConfig config) {

        return (exchange, chain) -> {

            // 通过exchange获取Request和Response对象:ServerHttpRequest --> HttpServletRequest
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            // 1.判断当前请求在不在拦截名单之中，不在说明不需要登录，直接放行即可
            List<String> paths = config.paths; // 请求的拦截名单
            String curPath = request.getURI().getPath(); // 当前请求路径
            // 如果拦截名单不为空，并且当前请求不以任意路径开头说明当前请求可以直接放行
            if (!CollectionUtils.isEmpty(paths) && !paths.stream().anyMatch(path -> curPath.startsWith(path))){
                return chain.filter(exchange);
            }

            // 2.获取token：cookie(同步) 或者 头信息（异步）
            String token = request.getHeaders().getFirst(properties.getToken());
            if (StringUtils.isBlank(token)){
                // 如果头信息获取失败，则尝试从cookie中获取
                MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                if (!CollectionUtils.isEmpty(cookies) && cookies.containsKey(properties.getCookieName())){
                    HttpCookie cookie = cookies.getFirst(properties.getCookieName());
                    token = cookie.getValue();
                }
            }

            // 3.判断token是否为空，如果为空则说明没有登录。重定向到登录页面，请求结束
            if (StringUtils.isBlank(token)){
                response.setStatusCode(HttpStatus.SEE_OTHER);
                response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                // 拦截
                return response.setComplete();
            }

            try {
                // 4.解析token验证是否合法，如果出现异常则说明token不合法（篡改、伪造、过期）。重定向到登录，请求结束
                Map<String, Object> map = JwtUtils.getInfoFromToken(token, properties.getPublicKey());

                // 5.验证当前请求的ip地址 和 登录用户的ip地址（载荷中的） 是否相同，如果不同则说明可能盗用，则重定向到登录页面，请求结束
                String curIp = IpUtils.getIpAddressAtGateway(request); // 当前请求的ip地址
                String ip = map.get("ip").toString(); // 登录用户的ip地址
                if (!StringUtils.equals(curIp, ip)) {
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                    // 拦截
                    return response.setComplete();
                }

                // 6.把载荷内容直接传递给后续服务，因为比较耗时   userId\ username
                request.mutate().header("userId", map.get("userId").toString())
                        .header("username", map.get("username").toString()).build();
                exchange.mutate().request(request).build();

                // 7.放行
                return chain.filter(exchange);
            } catch (Exception e) {
                response.setStatusCode(HttpStatus.SEE_OTHER);
                response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                // 拦截
                return response.setComplete();
            }
        };
    }

    @Data
    public static class PathConfig {
        private List<String> paths;
    }
}
